#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

MODULE_LICENSE("Dual BSD/GPL");
#define PROC_DIR     "hello"
#define PROC_RW_FILE "rw_file" // readonly proc file

static char* file_rw_data; // 保存 proc rw file 数据

static struct proc_dir_entry* p_dir = NULL;
static struct proc_dir_entry* p_file_rw = NULL;

// seq_read 和 .open 都会调用此函数
int ProcFs_read_rw_file(struct seq_file* file, void* p)
{
    if (file_rw_data == NULL)
        seq_printf(file, "%s\n", "(null)");
    else
        seq_printf(file, "%s", file_rw_data);
    return 0;
}

int ProcFs_open(struct inode* inode, struct file* file)
{
    return single_open(file, &ProcFs_read_rw_file, NULL);
}

ssize_t ProcFs_write_file(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
    long copyVal;

    // check user buffer
    if (unlikely(!access_ok(buf, count)))
        return -EFAULT;

    if (!IS_ERR_OR_NULL(file_rw_data)) {
        kfree(file_rw_data);
        file_rw_data = NULL;
    }

    file_rw_data = kmalloc(count + 1, GFP_NOFS); // +1 for zero-term
    if (unlikely(!file_rw_data)) {
        printk(KERN_ERR "kmalloc %ld failed\n", count + 1);
        return -EFAULT;
    }
    file_rw_data[count] = '\0'; // +1 for zero-term

    copyVal = __copy_from_user(file_rw_data, buf, count);
    if (copyVal != 0) {
        printk(KERN_ERR "Hello, world\n");
        return -EFAULT;
    }

    printk("hello_init: write proc file %s/%s success, count=%ld\n", PROC_DIR, PROC_RW_FILE, count);
    return count;
}

static struct file_operations proc_rd_name_fops = {
    .owner = THIS_MODULE,
    .open = ProcFs_open,
    .release = single_release,
    .read = seq_read,
    .llseek = seq_lseek,
    .write = ProcFs_write_file,
};

static int hello_init(void)
{
    p_dir = proc_mkdir(PROC_DIR, NULL);
    if (!p_dir) {
        printk(KERN_ERR "Failed to create proc dir: /proc/%s\n", PROC_DIR);
        return -EINVAL;
    }

    // 创建只读 proc file
    p_file_rw = proc_create_data(PROC_RW_FILE, S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP, p_dir, &proc_rd_name_fops, NULL);

    if (!p_file_rw) {
        printk("hello_init: creat name node failed!!!\n");
        proc_remove(p_dir);
        return -EINVAL;
    } else
        printk("hello_init: creat proc/%s/%s success\n", PROC_DIR, PROC_RW_FILE);

    printk(KERN_INFO "Success to insmod hello.ko\n");
    return 0;
}

static void hello_exit(void)
{
    if (!IS_ERR_OR_NULL(file_rw_data)) {
        kfree(file_rw_data);
        file_rw_data = NULL;
    }
    proc_remove(p_dir);
    printk(KERN_INFO "hello_exit: Success to rm proc/%s success\n", PROC_DIR);
}

module_init(hello_init);
module_exit(hello_exit);
